﻿
#ifndef Model_h
#define Model_h

#include <Riccsson/Xna/Framework/Graphics/GraphicsDevice.h>
#include <Riccsson/Xna/Framework/Graphics/ModelBone.h>

#include <System/Collections/Generic/RList.h>

namespace Riccsson
{
	namespace Xna
	{
		namespace Framework
		{
			namespace Graphics
			{
				class Model
				{
					private: GraphicsDevice* graphicsDevice;
					private: Riccsson::System::Collections::Generic::List<ModelBone> bones;
					private: Riccsson::System::Collections::Generic::List<ModelMesh> meshes;

					// Summary:
					//     Gets a collection of ModelBone objects which describe how each mesh in the
					//     Meshes collection for this model relates to its parent mesh.
					public: PROP3_GET(GC_PTR<ModelBoneCollection>, Bones)
					{
						throw;
					}
					private: PROP3_SET(GC_PTR<ModelBoneCollection>, Bones)
					{
						throw;
					}

					//
					// Summary:
					//     Gets a collection of ModelMesh objects which compose the model. Each ModelMesh
					//     in a model may be moved independently and may be composed of multiple materials
					//     identified as ModelMeshPart objects.
					public: PROP3_GET(GC_PTR<ModelMeshCollection>, Meshes)
					{
						throw;
					}
					private: PROP3_SET(GC_PTR<ModelMeshCollection>, Meshes)
					{
						throw;
					}

					//
					// Summary:
					//     Gets the root bone for this model.
					public: PROP3_GET(GC_PTR<ModelBone>, Root)
					{
						throw;
					}
					public: PROP3_SET(GC_PTR<ModelBone>, Root)
					{
						throw;
					}

					//
					// Summary:
					//     Gets or sets an object identifying this model.
					public: PROP3_GET(GC_PTR<Riccsson::System::object>, Tag)
					{
						throw;
					}
					public: PROP3_SET(GC_PTR<Riccsson::System::object>, Tag)
					{
						throw;
					}

					public: Model()
					{

					}

					public: Model(GraphicsDevice* graphicsDevice, Riccsson::System::Collections::Generic::List<ModelBone>* bones, Riccsson::System::Collections::Generic::List<ModelMesh>* meshes)
					{
						// TODO: Complete member initialization
						this->bones = bones;
						this->meshes = meshes;
						this->graphicsDevice = graphicsDevice;

						Bones = new ModelBoneCollection(bones);
						Meshes = new ModelMeshCollection(meshes);
					}
		
					public: void BuildHierarchy()
					{
						Matrix globalScale = Matrix::CreateScale(0.01f);
			
						foreach(var node : this->Root.Children)
						{
							BuildHierarchy(node, this->Root.Transform * globalScale, 0);
						}
					}
		
					private: void BuildHierarchy(ModelBone* node, Matrix parentTransform, int level)
					{
						node.ModelTransform = node.Transform * parentTransform;
			
						foreach (var child : *node.Children) 
						{
							BuildHierarchy(child, node.ModelTransform, level + 1);
						}
			
						//string s = string.Empty;
						//
						//for (int i = 0; i < level; i++) 
						//{
						//	s += "\t";
						//}
						//
						//Debug.WriteLine("{0}:{1}", s, node.Name);
					}
		
					public: void Draw(Matrix world, Matrix view, Matrix projection) 
					{       			
						// Look up combined bone matrices for the entire model.
						Riccsson::System::Array<Matrix> boneTransforms(bones.Count);
						CopyAbsoluteBoneTransformsTo(&boneTransforms);

						// Draw the model.
						foreach (ModelMesh* mesh : *Meshes)
						{
							foreach (Effect* effect : *mesh.Effects)
							{
								IEffectMatrices* effectMatricies = (IEffectMatrices*)effect;
								if (effectMatricies == null) {
									throw Riccsson::System::InvalidOperationException();
								}
								effectMatricies.World = boneTransforms[mesh.ParentBone.Index] * world;
								effectMatricies.View = view;
								effectMatricies.Projection = projection;
							}

							mesh.Draw();
						}
					}
		
					public: void CopyAbsoluteBoneTransformsTo(Riccsson::System::Array<Matrix>* destinationBoneTransforms)
					{
						if (destinationBoneTransforms == null)
							throw Riccsson::System::ArgumentNullException("destinationBoneTransforms");
						if (destinationBoneTransforms.Length < this->bones.Count)
							throw Riccsson::System::ArgumentOutOfRangeException("destinationBoneTransforms");
						int count = this->bones.Count;
						for (int index1 = 0; index1 < count; ++index1)
						{
							ModelBone* modelBone = (this->bones)[index1];
							if (modelBone->Parent == null)
							{
								destinationBoneTransforms[index1] = modelBone->transform;
							}
							else
							{
								int index2 = modelBone.Parent.Index;
								Matrix::Multiply(/*ref*/ modelBone.transform, /*ref*/ destinationBoneTransforms[index2], /*out*/ destinationBoneTransforms[index1]);
							}
						}
					}
				};
			}
		}
	}
}

#endif